"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
module.exports = function init(modules) {
    const ts = modules.typescript;
    const [majorVersion, minorVersion] = ts.version.split('.').map(p => Number(p));
    function checkRequiredVersion(requiredMajor, requiredMinor) {
        return requiredMajor < majorVersion || ((majorVersion === requiredMajor) && requiredMinor <= minorVersion);
    }
    function decorate(languageService, logger) {
        const intercept = Object.create(null);
        if (!checkRequiredVersion(3 /* major */, 7 /* minor */)) {
            logger === null || logger === void 0 ? void 0 : logger.msg(`typescript-vscode-sh-plugin not active, version ${3 /* major */}.${7 /* minor */} required, is ${ts.version}`, ts.server.Msg.Info);
            return languageService;
        }
        const olderThan42 = majorVersion < 4 || majorVersion === 4 && minorVersion < 2;
        logger === null || logger === void 0 ? void 0 : logger.msg(`typescript-vscode-sh-plugin active for version < 4.2 and JS/JSX files. Current version : ${ts.version}`, ts.server.Msg.Info);
        logger === null || logger === void 0 ? void 0 : logger.msg(`Intercepting getEncodedSemanticClassifications and getEncodedSyntacticClassifications.`, ts.server.Msg.Info);
        intercept.getEncodedSemanticClassifications = (filename, span, format) => {
            if (olderThan42 || filename.match(/\.js(x)?$/)) {
                return {
                    spans: getSemanticTokens(languageService, filename, span),
                    endOfLineState: ts.EndOfLineState.None
                };
            }
            return languageService.getEncodedSemanticClassifications(filename, span, format);
        };
        intercept.getEncodedSyntacticClassifications = (_filename, _span) => {
            return {
                spans: [],
                endOfLineState: ts.EndOfLineState.None
            };
        };
        return new Proxy(languageService, {
            get: (target, property) => {
                return intercept[property] || target[property];
            },
        });
    }
    function getSemanticTokens(jsLanguageService, fileName, span) {
        let resultTokens = [];
        const collector = (node, typeIdx, modifierSet) => {
            resultTokens.push(node.getStart(), node.getWidth(), ((typeIdx + 1) << 8 /* typeOffset */) + modifierSet);
        };
        const program = jsLanguageService.getProgram();
        if (program) {
            const sourceFile = program.getSourceFile(fileName);
            if (sourceFile) {
                collectTokens(program, sourceFile, span, collector);
            }
        }
        return resultTokens;
    }
    function collectTokens(program, sourceFile, span, collector) {
        const typeChecker = program.getTypeChecker();
        let inJSXElement = false;
        function visit(node) {
            if (!node || !ts.textSpanIntersectsWith(span, node.pos, node.getFullWidth()) || node.getFullWidth() === 0) {
                return;
            }
            const prevInJSXElement = inJSXElement;
            if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
                inJSXElement = true;
            }
            if (ts.isJsxExpression(node)) {
                inJSXElement = false;
            }
            if (ts.isIdentifier(node) && !inJSXElement && !inImportClause(node)) {
                let symbol = typeChecker.getSymbolAtLocation(node);
                if (symbol) {
                    if (symbol.flags & ts.SymbolFlags.Alias) {
                        symbol = typeChecker.getAliasedSymbol(symbol);
                    }
                    let typeIdx = classifySymbol(symbol, getMeaningFromLocation(node));
                    if (typeIdx !== undefined) {
                        let modifierSet = 0;
                        if (node.parent) {
                            const parentIsDeclaration = (ts.isBindingElement(node.parent) || tokenFromDeclarationMapping[node.parent.kind] === typeIdx);
                            if (parentIsDeclaration && node.parent.name === node) {
                                modifierSet = 1 << 0 /* declaration */;
                            }
                        }
                        // property declaration in constructor
                        if (typeIdx === 6 /* parameter */ && isRightSideOfQualifiedNameOrPropertyAccess(node)) {
                            typeIdx = 9 /* property */;
                        }
                        typeIdx = reclassifyByType(typeChecker, node, typeIdx);
                        const decl = symbol.valueDeclaration;
                        if (decl) {
                            const modifiers = ts.getCombinedModifierFlags(decl);
                            const nodeFlags = ts.getCombinedNodeFlags(decl);
                            if (modifiers & ts.ModifierFlags.Static) {
                                modifierSet |= 1 << 1 /* static */;
                            }
                            if (modifiers & ts.ModifierFlags.Async) {
                                modifierSet |= 1 << 2 /* async */;
                            }
                            if (typeIdx !== 0 /* class */ && typeIdx !== 2 /* interface */) {
                                if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const) || (symbol.getFlags() & ts.SymbolFlags.EnumMember)) {
                                    modifierSet |= 1 << 3 /* readonly */;
                                }
                            }
                            if ((typeIdx === 7 /* variable */ || typeIdx === 10 /* function */) && isLocalDeclaration(decl, sourceFile)) {
                                modifierSet |= 1 << 5 /* local */;
                            }
                            if (program.isSourceFileDefaultLibrary(decl.getSourceFile())) {
                                modifierSet |= 1 << 4 /* defaultLibrary */;
                            }
                        }
                        else if (symbol.declarations && symbol.declarations.some(d => program.isSourceFileDefaultLibrary(d.getSourceFile()))) {
                            modifierSet |= 1 << 4 /* defaultLibrary */;
                        }
                        collector(node, typeIdx, modifierSet);
                    }
                }
            }
            ts.forEachChild(node, visit);
            inJSXElement = prevInJSXElement;
        }
        visit(sourceFile);
    }
    function classifySymbol(symbol, meaning) {
        const flags = symbol.getFlags();
        if (flags & ts.SymbolFlags.Class) {
            return 0 /* class */;
        }
        else if (flags & ts.SymbolFlags.Enum) {
            return 1 /* enum */;
        }
        else if (flags & ts.SymbolFlags.TypeAlias) {
            return 5 /* type */;
        }
        else if (flags & ts.SymbolFlags.Interface) {
            if (meaning & 2 /* Type */) {
                return 2 /* interface */;
            }
        }
        else if (flags & ts.SymbolFlags.TypeParameter) {
            return 4 /* typeParameter */;
        }
        let decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
        if (decl && ts.isBindingElement(decl)) {
            decl = getDeclarationForBindingElement(decl);
        }
        return decl && tokenFromDeclarationMapping[decl.kind];
    }
    function reclassifyByType(typeChecker, node, typeIdx) {
        // type based classifications
        if (typeIdx === 7 /* variable */ || typeIdx === 9 /* property */ || typeIdx === 6 /* parameter */) {
            const type = typeChecker.getTypeAtLocation(node);
            if (type) {
                const test = (condition) => {
                    return condition(type) || type.isUnion() && type.types.some(condition);
                };
                if (typeIdx !== 6 /* parameter */ && test(t => t.getConstructSignatures().length > 0)) {
                    return 0 /* class */;
                }
                if (test(t => t.getCallSignatures().length > 0) && !test(t => t.getProperties().length > 0) || isExpressionInCallExpression(node)) {
                    return typeIdx === 9 /* property */ ? 11 /* member */ : 10 /* function */;
                }
            }
        }
        return typeIdx;
    }
    function isLocalDeclaration(decl, sourceFile) {
        if (ts.isBindingElement(decl)) {
            decl = getDeclarationForBindingElement(decl);
        }
        if (ts.isVariableDeclaration(decl)) {
            return (!ts.isSourceFile(decl.parent.parent.parent) || ts.isCatchClause(decl.parent)) && decl.getSourceFile() === sourceFile;
        }
        else if (ts.isFunctionDeclaration(decl)) {
            return !ts.isSourceFile(decl.parent) && decl.getSourceFile() === sourceFile;
        }
        return false;
    }
    function getDeclarationForBindingElement(element) {
        while (true) {
            if (ts.isBindingElement(element.parent.parent)) {
                element = element.parent.parent;
            }
            else {
                return element.parent.parent;
            }
        }
    }
    function inImportClause(node) {
        const parent = node.parent;
        return parent && (ts.isImportClause(parent) || ts.isImportSpecifier(parent) || ts.isNamespaceImport(parent));
    }
    function isExpressionInCallExpression(node) {
        while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
            node = node.parent;
        }
        return ts.isCallExpression(node.parent) && node.parent.expression === node;
    }
    function isRightSideOfQualifiedNameOrPropertyAccess(node) {
        return (ts.isQualifiedName(node.parent) && node.parent.right === node) || (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node);
    }
    function getMeaningFromLocation(node) {
        const f = ts.getMeaningFromLocation;
        if (typeof f === 'function') {
            return f(node);
        }
        return 7 /* All */;
    }
    const tokenFromDeclarationMapping = {
        [ts.SyntaxKind.VariableDeclaration]: 7 /* variable */,
        [ts.SyntaxKind.Parameter]: 6 /* parameter */,
        [ts.SyntaxKind.PropertyDeclaration]: 9 /* property */,
        [ts.SyntaxKind.ModuleDeclaration]: 3 /* namespace */,
        [ts.SyntaxKind.EnumDeclaration]: 1 /* enum */,
        [ts.SyntaxKind.EnumMember]: 8 /* enumMember */,
        [ts.SyntaxKind.ClassDeclaration]: 0 /* class */,
        [ts.SyntaxKind.MethodDeclaration]: 11 /* member */,
        [ts.SyntaxKind.FunctionDeclaration]: 10 /* function */,
        [ts.SyntaxKind.FunctionExpression]: 10 /* function */,
        [ts.SyntaxKind.MethodSignature]: 11 /* member */,
        [ts.SyntaxKind.GetAccessor]: 9 /* property */,
        [ts.SyntaxKind.SetAccessor]: 9 /* property */,
        [ts.SyntaxKind.PropertySignature]: 9 /* property */,
        [ts.SyntaxKind.InterfaceDeclaration]: 2 /* interface */,
        [ts.SyntaxKind.TypeAliasDeclaration]: 5 /* type */,
        [ts.SyntaxKind.TypeParameter]: 4 /* typeParameter */,
        [ts.SyntaxKind.PropertyAssignment]: 9 /* property */,
        [ts.SyntaxKind.ShorthandPropertyAssignment]: 9 /* property */
    };
    return {
        create(info) {
            return decorate(info.languageService, info.project.projectService.logger);
        },
        onConfigurationChanged(_config) {
        },
        // added for testing
        decorate(languageService) {
            return decorate(languageService);
        }
    };
};
//# sourceMappingURL=index.js.map